跨 Memory Context 机制
1. 文档目标
这份文档聚焦 src/ 里“memory 如何跨不同 context 流动”这一条主线。
这里的 context 不是单指模型上下文窗口,而是几种边界同时存在:
- 主对话与 forked subagent 之间
- 当前会话与后续会话之间
- 个人记忆与 team 共享记忆之间
- 主线程 agent 与具名 agent 类型之间
- 原始长对话与 compact 之后的新上下文之间
从源码看,Claude Code 并没有做一个统一的“大 memory 总线”,而是把 memory 拆成几类职责不同、边界不同的存储,再通过 prompt 注入、fork 继承、文件系统、同步服务把它们串起来。
核心入口主要在:
src/services/SessionMemory/sessionMemory.tssrc/services/compact/sessionMemoryCompact.tssrc/services/extractMemories/extractMemories.tssrc/memdir/memdir.tssrc/memdir/paths.tssrc/memdir/teamMemPaths.tssrc/services/teamMemorySync/src/tools/AgentTool/agentMemory.tssrc/utils/forkedAgent.tssrc/utils/systemPrompt.ts
2. 先区分仓库里的几类 memory
这套实现里至少有四类 memory:
| 类型 | 主要作用 | 典型路径 | 作用范围 |
|---|---|---|---|
| session memory | 给当前长会话做浓缩摘要,优先服务 compact | {projectDir}/{sessionId}/session-memory/summary.md | 只对当前 session 生效 |
| auto memory | 沉淀跨会话可复用信息 | ~/.claude/projects/<project>/memory/ 或 override 路径 | 同一用户、同一项目 |
| team memory | 在项目维度共享长期记忆 | <autoMemPath>/team/ | 同仓库、同组织成员 |
| agent memory | 给某个 agent 类型持久化经验 | agent-memory/ 或 agent-memory-local/ | 同 agent 类型、按 scope 控制 |
它们不是同一个生命周期:
session memory面向“当前对话压缩续航”auto memory面向“下次还记得”team memory面向“别人也能继承”agent memory面向“这个 agent 类型持续自我积累”
3. 跨 Context 的共性设计
虽然几类 memory 分工不同,但源码里有三条共性原则。
3.1 用 prompt 注入把 memory 变成当前上下文
memory 真正进入模型可见上下文,主要靠 prompt 注入,而不是额外挂载一个数据库查询层。
src/constants/prompts.ts在系统 prompt 组装阶段调用loadMemoryPrompt()src/memdir/memdir.ts根据是否启用 auto/team memory,构造单目录或双目录 memory promptsrc/utils/systemPrompt.ts在 agent 有memory配置时,用loadAgentMemoryPrompt()把 agent memory 追加进 agent 的 system prompt
因此“跨 context”的第一步其实是:先把持久化文件重新翻译成 prompt 里的行为说明和索引内容。
3.2 用 forked agent 共享上下文,但隔离可变状态
memory 更新通常不是主线程直接做,而是 fork 一个子执行链去做。
关键点在 src/utils/forkedAgent.ts:
createCacheSafeParams()把 system prompt、user context、system context、tools、历史消息打包出来runForkedAgent()复用这些 cache-safe 参数,尽量命中父会话的 prompt cachecreateSubagentContext()默认克隆readFileState、contentReplacementState,并给子链路新的 abort/controller 与 no-op 状态回调
也就是说:
- 可见上下文尽量继承,保证 fork 和主线程看到的是同一份对话语义
- 可变运行态尽量隔离,避免 session memory / extract memories 之类的后台动作污染主线程状态
这就是“跨 context 传语义,不传脏状态”。
3.3 用文件路径和权限规则做 memory 边界
memory 最终都落到文件系统,所以边界控制也主要靠路径。
src/utils/permissions/filesystem.ts为 session memory、auto memory、agent memory 提供专门的读写许可分支src/memdir/teamMemPaths.ts对 team memory 的写路径做两段校验:先resolve(),再realpath(),防止..和 symlink 逃逸src/utils/memoryFileDetection.ts/src/utils/sessionFileAccessHooks.ts统一识别 memory 文件,补充 telemetry 和 watcher 触发
因此这里不是“逻辑上说它是 memory”,而是“路径进入某个受控目录后,权限、同步、统计都按 memory 规则处理”。
4. 链路一:主会话 -> Session Memory -> Compact
这是最典型的“当前上下文跨边界压缩续航”路径。
4.1 Session memory 只在主 REPL 线程更新
src/services/SessionMemory/sessionMemory.ts 的 extractSessionMemory() 明确限制:
querySource必须是repl_main_thread- remote mode 不启用
- auto compact 关闭时也不注册这个 hook
初始化入口是 initSessionMemory(),它通过 registerPostSamplingHook() 在每轮主查询结束后判断要不要更新 session memory。
4.2 触发条件是“上下文已足够大”,而不是每轮都写
shouldExtractMemory() 组合了几类阈值:
- 会话 token 总量达到初始化门槛
- 距离上次提取又增长了足够 token
- 工具调用数达到阈值,或者至少已经来到一个“末尾没有 tool call”的自然停顿点
对应状态保存在 src/services/SessionMemory/sessionMemoryUtils.ts:
tokensAtLastExtractionlastSummarizedMessageIdextractionStartedAt
这说明 session memory 不是实时镜像,而是“阶段性浓缩快照”。
4.3 真正写文件的是一个受限 forked agent
提取流程是:
主线程消息
-> setupSessionMemoryFile()
-> buildSessionMemoryUpdatePrompt()
-> runForkedAgent(querySource='session_memory')
-> 只允许 Edit summary.md
这里有两个关键点:
setupSessionMemoryFile()会创建summary.md,并在首次创建时写入模板createMemoryFileCanUseTool()只允许对子文件执行FileEdit
也就是说,session memory fork 并不是一个通用 agent,它本质上是一个“只准改当前 session 摘要文件”的单用途上下文转换器。
4.4 Compact 优先消费 session memory,而不是重新总结整段对话
src/services/compact/sessionMemoryCompact.ts 里的 trySessionMemoryCompaction() 是这条链路的下游消费者。
它的流程是:
compact 前
-> waitForSessionMemoryExtraction()
-> getLastSummarizedMessageId()
-> getSessionMemoryContent()
-> calculateMessagesToKeepIndex()
-> 用 session memory 作为 compact summary
-> buildPostCompactMessages()
这条路径的意义是:
- 已经被 session memory 覆盖的历史,不必再次调用 compact summary 模型
- 只保留摘要之后的一小段 tail,维持 API 合法性和最近交互细节
- 仍然会通过
processSessionStartHooks('compact')恢复 CLAUDE.md 等开场上下文
所以 session memory 的本质不是“给用户看的笔记”,而是“预先计算好的 compact 中间态”。
4.5 它如何跨过 compact 边界继续生效
createCompactionResultFromSessionMemory() 会:
- 构造新的
compact_boundary - 把
session memory变成一条isCompactSummary的 user message - 记录
messagesToKeep - 在 boundary 上标注
preservedSegment
因此 compact 后的新上下文,不是凭空重建,而是:
旧长对话
-> session memory 摘要
+ 保留的 tail
+ compact 后恢复的附件/钩子
= 新对话上下文
这就是第一种跨 context:同一 session 内,长历史被提前折叠成 session memory,再跨过 compact 边界续接。
5. 链路二:主会话 -> Auto / Team Memory -> 后续会话
这条链路解决的是“这次聊过的东西,下次怎么还在”。
5.1 Auto memory 先通过 system prompt 暴露给当前会话
src/memdir/memdir.ts 的 loadMemoryPrompt() 会把 memory 系统装进 system prompt:
- 只开 auto memory 时,注入 private memory 目录说明
- 同时开 team memory 时,注入 private + team 双目录说明
- assistant/kairos 模式下,改成 daily log 追加式记忆
这一步只注入行为规范和 MEMORY.md 索引,不会自动把所有 topic 文件全文塞进 prompt。
5.2 会话结束点由后台提取器把新信息沉淀出来
src/services/extractMemories/extractMemories.ts 负责在 query loop 完成后做 durable memory 提取。
它的特点:
- 也是
runForkedAgent(),共享父对话 cache-safe 上下文 createAutoMemCanUseTool()只允许 Read/Grep/Glob、只读 Bash,以及对 memory 目录内的 Write/Edit- 如果主线程已经直接写过 memory 文件,
hasMemoryWritesSince()会跳过本轮提取,避免重复劳动
这意味着主线程和后台 extractor 的关系不是竞争,而是互补:
- 主线程自己记了,就不再 fork
- 主线程没记,后台 extractor 补记
5.3 Team memory 不是另一套 prompt,而是 auto memory 的共享分支
当 TEAMMEM 启用时,buildCombinedMemoryPrompt() 会告诉模型:
- private memory 写到 auto memory 根目录
- team memory 写到
<autoMemPath>/team/ - 两边各有自己的
MEMORY.md索引
所以 team memory 不是独立系统,而是 auto memory 目录树里的共享子空间。
5.4 跨成员传播靠 Team Memory Sync
src/services/teamMemorySync/ 把 team memory 从本地目录同步到服务端。
核心机制:
watcher.ts启动时先pullTeamMemory()- 然后对 team 目录开
fs.watch,本地有变更时 debounce 后pushTeamMemory() index.ts里 pull 是“服务端覆盖本地”,push 是“只上传 checksum 有变化的 key”
同步 scope 不是靠本地目录名,而是靠 repo slug:
- 通过
getGithubRepo()识别仓库 - 服务端按 repo 维度存 team memory
于是第二种跨 context 出现了:
当前会话
-> extract memories 写本地 memory 文件
-> 下次 session 再由 loadMemoryPrompt 注入
-> 若写入 team 子目录,再由 watcher 推到服务端
-> 其他成员 session 启动时 pull 下来
这条链把“会话上下文”扩展成了“项目长期上下文”和“团队共享上下文”。
6. 链路三:Agent 定义 -> Agent Memory -> 同类型 Agent
这条链路解决的是“不同 agent 类型如何拥有自己的长期记忆”。
6.1 Agent memory 是 agent definition 的一个 frontmatter 能力
src/tools/AgentTool/loadAgentsDir.ts 里,agent 定义支持:
memory: usermemory: projectmemory: local
一旦配置了 memory:
getSystemPrompt()会自动把loadAgentMemoryPrompt()追加进 agent 的 system prompt- 如果 agent 自己声明了 tools,还会自动补上
FileRead/FileWrite/FileEdit,保证它能操作 memory 文件
所以 agent memory 不是外部服务,而是 agent prompt 的内建能力。
6.2 三种 scope 对应三种持久化边界
src/tools/AgentTool/agentMemory.ts 中:
user:~/.claude/agent-memory/<agentType>/project:<cwd>/.claude/agent-memory/<agentType>/local:<cwd>/.claude/agent-memory-local/<agentType>/,或 remote mount 下的 project namespaced 路径
这说明 agent memory 的“跨 context”不是跨成员,而是跨 agent 实例:
- 只要 agentType 相同,后续实例都能读到这份 memory
- 但 scope 决定它是用户级共享、项目级共享,还是仅本机局部共享
6.3 项目还可以给 agent memory 提供 snapshot 种子
src/tools/AgentTool/agentMemorySnapshot.ts 额外提供一层“项目预置 agent 记忆”:
- snapshot 放在
<cwd>/.claude/agent-memory-snapshots/<agentType>/ - 首次本地 memory 为空时,可
initializeFromSnapshot() - snapshot 更新后,还能检测到“需要提示更新”
这让 agent memory 既能自我积累,也能由项目维护者预灌初始知识。
7. 这套“跨 Memory Context”机制到底跨了什么
如果把几条链路合在一起,可以把它概括成下面这张图:
flowchart TD
A["主对话 messages"] --> B["Session Memory Hook"]
B --> C["session-memory/summary.md"]
C --> D["sessionMemoryCompact"]
D --> E["compact 后的新上下文"]
A --> F["extractMemories fork"]
F --> G["auto memory"]
F --> H["team memory"]
G --> I["下次 session 的 system prompt"]
H --> J["TeamMemorySync"]
J --> K["其他成员 session"]
L["agent definition(memory scope)"] --> M["agent memory prompt"]
M --> N["同类型 agent 的后续实例"]
O["project snapshot"] --> N
从源码层面,所谓“跨 memory context”主要体现在四件事:
- 同一会话内跨上下文窗口:长对话先折叠成
session memory,再穿过 compact 边界续接 - 跨会话:本轮对话经
extractMemories落盘为 auto memory,下次再注入 prompt - 跨成员:team memory 通过 repo 维度同步,把本地共享子目录扩展成组织内共享上下文
- 跨 agent 类型实例:agent memory 让某个 agentType 的经验可以在后续实例中复用
8. 设计上的几个关键判断
8.1 它不是“一个 memory 系统”,而是“多层记忆分层”
源码里没有试图用一种存储覆盖所有需求,而是按时间跨度拆层:
- 短期:session memory
- 中长期:auto memory
- 团队长期:team memory
- agent 专属长期:agent memory
这种分层让 compact、协作、agent 自我改进不会互相打架。
8.2 它不是直接共享运行态,而是共享可重建的语义
真正跨 context 传播的核心对象不是 React state,也不是 query loop 的内部变量,而是:
- fork 时共享的 cache-safe prompt 语义
- 文件系统里的 memory 内容
- compact 后的 boundary + summary + preserved tail
运行态本身大多通过 createSubagentContext() 被隔离掉了。
8.3 文件系统是这套机制的真实“记忆总线”
无论是哪种 memory,最后都落在文件上:
- session memory:
summary.md - auto/team memory:
MEMORY.md+ topic files - agent memory:按 agentType 分目录
所以权限、同步、搜索、统计都围绕路径展开。这也是为什么 filesystem.ts、memoryFileDetection.ts、teamMemPaths.ts 在这套机制里和业务逻辑同等关键。
9. 一句话总结
Claude Code 的“跨 memory context”机制,本质上是:
用 forked agent 在不污染主线程状态的前提下,把当前对话提炼成不同层级的文件化记忆,再通过 prompt 注入、compact 边界恢复和 team sync,把这些记忆重新投射回后续的 session、agent 和协作成员上下文中。
10. 相关源码
src/services/SessionMemory/sessionMemory.tssrc/services/SessionMemory/sessionMemoryUtils.tssrc/services/SessionMemory/prompts.tssrc/services/compact/sessionMemoryCompact.tssrc/services/extractMemories/extractMemories.tssrc/memdir/memdir.tssrc/memdir/paths.tssrc/memdir/teamMemPaths.tssrc/services/teamMemorySync/index.tssrc/services/teamMemorySync/watcher.tssrc/tools/AgentTool/agentMemory.tssrc/tools/AgentTool/agentMemorySnapshot.tssrc/tools/AgentTool/loadAgentsDir.tssrc/utils/forkedAgent.tssrc/utils/systemPrompt.ts